#include "scene.hpp"
#include <iostream>

typedef std::list<SceneNode*> GeometryList;
extern GeometryList m_primitives;
int counter = 0;

SceneNode::SceneNode(const std::string& name)
  : m_name(name)
{
}

SceneNode::~SceneNode()
{
}

void SceneNode::rotate(char axis, double angle)
{
  // Fill me in
	angle = angle/180*M_PI;
    Vector4D row1, row2, row3, row4;
	switch( axis ){
		case 'x':
			row1 = Vector4D(1.0, 0.0, 0.0, 0.0);
			row2 = Vector4D(0.0, cos(angle), -sin(angle), 0.0);
			row3 = Vector4D(0.0, sin(angle), cos(angle), 0.0);
			row4 = Vector4D(0.0, 0.0, 0.0, 1.0);
			break;
		case 'y':
			row1 = Vector4D(cos(angle), 0.0, sin(angle), 0.0);
			row2 = Vector4D(0.0, 1.0, 0.0, 0.0);
			row3 = Vector4D(-sin(angle), 0.0, cos(angle), 0.0);
			row4 = Vector4D(0.0, 0.0, 0.0, 1.0);
			break;
		case 'z':
			row1 = Vector4D(cos(angle), -sin(angle), 0.0, 0.0);
			row2 = Vector4D(sin(angle), cos(angle), 0.0, 0.0);
			row3 = Vector4D(0.0, 0.0, 1.0, 0.0);
			row4 = Vector4D(0.0, 0.0, 0.0, 1.0);
			break;
	}
	Matrix4x4 rot = Matrix4x4(row1, row2, row3, row4);
    set_transform( get_transform() * rot );
}

void SceneNode::scale(const Vector3D& amount)
{
  // Fill me in
	Vector4D row1( amount[0], 0.0, 0.0, 0.0);
	Vector4D row2( 0.0, amount[1], 0.0, 0.0);
	Vector4D row3( 0.0, 0.0, amount[2], 0.0);
	Vector4D row4( 0.0, 0.0, 0.0, 1.0);
	
	Matrix4x4 scale = Matrix4x4(row1, row2,row3, row4);
    set_transform( get_transform() * scale );
}

void SceneNode::translate(const Vector3D& amount)
{
  // Fill me in
	Vector4D row1( 1.0, 0.0, 0.0, amount[0]);
	Vector4D row2( 0.0, 1.0, 0.0, amount[1]);
	Vector4D row3( 0.0, 0.0, 1.0, amount[2]);
	Vector4D row4( 0.0, 0.0, 0.0, 1.0);
	
	Matrix4x4 translate = Matrix4x4(row1, row2,row3, row4);
    set_transform( get_transform() * translate );
}

bool SceneNode::is_joint() const
{
  return false;
}

bool SceneNode::is_geometry() const
{
	return false;
}

bool SceneNode::ray_intersect(Ray ray, Intersection *intersection)
{
	double MAX_DIST = 1000000.0f;
	bool hit = false;
	ray.set_position(m_invtrans * ray.get_position());
	ray.set_direction(m_invtrans * ray.get_direction());
	for (ChildList::const_iterator node = m_children.begin(); node != m_children.end(); node++) {
		bool temp_hit = (*node)->ray_intersect(ray, intersection);
		if( temp_hit && intersection->m_dist < MAX_DIST){
			MAX_DIST = intersection->m_dist;
		}
		if( temp_hit ) hit = true;
    }
	if(hit){
		intersection->m_normal = (m_invtrans.transpose()) * intersection->m_normal;
		intersection->m_intersect = m_trans * intersection->m_intersect;
	}
	return hit;
}

void SceneNode::flat_hier(const Matrix4x4& trans)
{
	for (ChildList::const_iterator node = m_children.begin(); node != m_children.end(); node++) {
		(*node)->flat_hier(trans*m_trans);
	}
}

JointNode::JointNode(const std::string& name)
  : SceneNode(name)
{
}

JointNode::~JointNode()
{
}

bool JointNode::is_joint() const
{
  return true;
}
 
void JointNode::set_joint_x(double min, double init, double max)
{
  m_joint_x.min = min;
  m_joint_x.init = init;
  m_joint_x.max = max;
}

void JointNode::set_joint_y(double min, double init, double max)
{
  m_joint_y.min = min;
  m_joint_y.init = init;
  m_joint_y.max = max;
}

GeometryNode::GeometryNode(const std::string& name, Primitive* primitive)
  : SceneNode(name),m_primitive(primitive),m_texture(NULL),m_bump(NULL)
{
	m_id = counter;
	counter++;
}

GeometryNode::~GeometryNode()
{
}

bool GeometryNode::is_geometry() const
{
	return true;
}

void GeometryNode::flat_hier(const Matrix4x4& trans)
{
	set_transform(trans * m_trans);
	m_primitives.push_back(this);
}

bool GeometryNode::ray_intersect(Ray ray, Intersection *intersection)
{
	ray.set_position(m_invtrans * ray.get_position());
	ray.set_direction(m_invtrans * ray.get_direction());
	bool hit = false;
	if(m_primitive){
		hit = m_primitive->RayIntersect(ray, intersection);
		if(hit){
			if(m_texture){
				intersection->m_texture = m_texture;
			}
			else{
				intersection->m_texture = NULL;
			}
			if(m_bump){
				Vector3D n = m_bump->extract_normal(intersection->u, intersection->v);
				n = Vector3D(2.0 * (n[0] - 0.5), 2.0 * (n[1] - 0.5), 2.0 * (n[2] - 0.5));
				intersection->m_normal =  intersection->m_normal + n;
				intersection->m_normal.normalize();
				intersection->m_bump = m_bump;
			}
			else{
				intersection->m_bump = NULL;
			}
			intersection->m_material = m_material;
			intersection->m_hit_node = this;
			intersection->m_normal = (m_invtrans.transpose()) * intersection->m_normal;
			intersection->m_intersect = m_trans * intersection->m_intersect;
		}
	}
	return hit;
}

void CSGNode::flat_hier(const Matrix4x4& trans)
{
	set_transform(trans * m_trans);
	m_primitives.push_back(this);
}

CSGNode::CSGNode(const std::string& name, int operation, 
			SceneNode* node1, SceneNode* node2): SceneNode(name)
{
	m_node1 = node1;
	m_node2 = node2;
	m_operation = operation;
}

bool CSGNode::ray_intersect(Ray ray, Intersection *intersection)
{
	ray.set_position(m_invtrans * ray.get_position());
	ray.set_direction(m_invtrans * ray.get_direction());
	Intersection inter1;
	Intersection inter2;
	bool hit = false;
	bool hit1 = m_node1->ray_intersect(ray, &inter1);
	bool hit2 = m_node2->ray_intersect(ray, &inter2);
	if(inter1.m_dist > intersection->m_dist) hit1 = false;
	if(inter2.m_dist > intersection->m_dist) hit2 = false;
	if(!hit1 && !hit2) return false;
	double t1 = inter1.m_dist, t2 = inter1.m_dist2;
	double T1 = inter2.m_dist, T2 = inter2.m_dist2;
	if( m_operation == 1 ){ // UNION
		if(hit1 && !hit2) *intersection = inter1;
		else if(!hit1 && hit2) *intersection = inter2;
		else{
			if(t1 < T1){
				if(t2 > T1 && t2 < T2){
					*intersection = inter1;
					intersection->m_dist2 = inter2.m_dist2;
					intersection->m_normal2 = inter2.m_normal2;
				}
				else{
					*intersection = inter1;
				}
			}
			else{
				if(T2 > t1 && T2 < t2){
					*intersection = inter2;
					intersection->m_dist2 = inter1.m_dist2;
					intersection->m_normal2 = inter1.m_normal2;
				}
				else{
					*intersection = inter2;
				}
			}
		}
		hit = true;
	}
	else if( m_operation == 2 ){ // INTER
		if(T1 > t1 && T1 < t2){
			*intersection = inter2;
			if(T1 < t1){
				intersection->m_dist2 = inter1.m_dist2;
				intersection->m_normal2 = inter1.m_normal2;
			}
			hit = true;
		}
		else if(t1 > T1 && t1 < T2){
			*intersection = inter1;
			if(T2 < t2){
				intersection->m_dist2 = inter2.m_dist2;
				intersection->m_normal2 = inter2.m_normal2;
			}
			hit = true;
		}
	}
	else if( m_operation == 3 ){ // DIFFERENCE
		if(hit1){
			if(hit2){
				if(T1 < t1 && T2 > t2){
					return false;
				}
				else if(T1 < t1 && T2 < t2 && t1 < T2){
                    *intersection = inter1;
                    intersection->m_intersect = ray.get_position()+T2*ray.get_direction();
                    intersection->m_dist = T2;
                    intersection->m_normal = -inter2.m_normal2;
                    intersection->m_dist2 = t2;
                    intersection->m_normal2 = inter1.m_normal2;
                    hit = true;
                }
                else if(t1 < T1 && t2 > T2){
                    *intersection = inter1;
                    hit = true;
                }
				else if(t1 > T1 && t1 < T2 && t2 > T2){
					*intersection = inter1;
					intersection->m_intersect = ray.get_position()+T2*ray.get_direction();
					intersection->m_normal = -inter2.m_normal2;
					intersection->m_dist = T2;
					intersection->m_dist2 = inter1.m_dist2;
					intersection->m_normal2 = inter1.m_normal2;
                    hit = true;
				}
				else if(t1 < T1 && T1 < t2 && t2 < T2){
					*intersection = inter1;
					intersection->m_dist2 = inter2.m_dist;
					intersection->m_normal2 = -inter2.m_normal;
                    hit = true;
                }
                else if((T1 < t1 && T2 < t1) || (t1 < T1 && t2 < T1 && T1 < T2)){ // no intersection
                    *intersection = inter1;
                    hit = true;
                }
			}
			else{
				*intersection = inter1;
                hit = true;
			}
        
        }
    }
    if(hit){
        intersection->m_normal = (m_invtrans.transpose()) * intersection->m_normal;
        intersection->m_intersect = m_trans * intersection->m_intersect;
        intersection->m_normal2 =  (m_invtrans.transpose()) * intersection->m_normal2;
	}
    return hit;
}

CSGNode::~CSGNode(){}